﻿namespace MicroCloud.AspNetCore.SignalR
{
    /// <summary>
    /// SignalR模块基类
    /// </summary>
    [Description("SignalR模块")]
    [DependsOnPacks(typeof(AspNetCorePack))]
    public abstract class SignalRPackBase : AspPackBase
    {
        #region 属性
        /// <summary>
        /// 获取 系统配置选项信息
        /// </summary>
        protected MicroCloudOptions Options { get; private set; }

        /// <summary>
        /// 获取 模块级别
        /// <para>级别越小越先启动</para>
        /// </summary>
        public override PackLevel Level => PackLevel.Application;

        /// <summary>
        /// 获取 模块启动顺序编号
        /// <para>模块启动的顺序先按级别启动，级别内部再按此顺序启动，级别默认为0，表示无依赖，需要在同级别有依赖顺序的时候，再重写为 >0 的顺序值。</para>
        /// </summary>
        public override int OrderNo => 0;

        #endregion

        #region 方法
        /// <summary>
        /// 将模块服务添加到依赖注入服务容器中
        /// </summary>
        /// <param name="services">依赖注入服务容器</param>
        /// <returns></returns>
        public override IServiceCollection AddServices(IServiceCollection services)
        {
            Options = services.GetMicroCloudOptions();
            if (Options?.SignalR?.Enabled != true)
            {
                return services;
            }

            services.AddSingleton<IUserIdProvider, UserIdProvider>();
            services.AddSingleton<IConnectionUserCache, ConnectionUserCache>();
            services.AddSingleton<ISignalRService, SignalRService>();

            Action<HubOptions> hubOptions = GetHubOptionsAction(services);
            ISignalRServerBuilder builder = hubOptions == null ? services.AddSignalR() : services.AddSignalR(hubOptions);

            //启用MessagePack协议支持
            if (Options.SignalR.MessagePackEnabled == true)
            {
                builder = builder.AddMessagePackProtocol();
            }

            //横向扩展 Scale-out 使用Sticky Sessions (粘性会话)
            var redisEnabled = Options?.Redis?.Enabled;
            if (redisEnabled == true)
            {
                string redisConfig = Options?.Redis?.Configuration;
                if (!redisConfig.IsMissing())
                {
                    builder = builder.AddStackExchangeRedis(redisConfig);
                }
            }

            Action<ISignalRServerBuilder> buildAction = GetSignalRServerBuildAction(services);
            buildAction?.Invoke(builder);

            return services;
        }
     
        /// <summary>
        /// 重写以获取HubOptions创建委托
        /// </summary>
        /// <param name="services">依赖注入服务容器</param>
        /// <returns></returns>
        protected virtual Action<HubOptions> GetHubOptionsAction(IServiceCollection services)
        {
            return config =>
            {
                //IWebHostEnvironment environment = services.GetWebHostEnvironment();
                ////开发环境下启用
                //if (environment.IsDevelopment())
                //{
                //    //设置是否向客户端发送详细的错误消息。详细的错误消息包括服务器上引发的异常的详细信息。
                //    config.EnableDetailedErrors = true;
                //}
#if DEBUG
                //设置是否向客户端发送详细的错误消息。详细的错误消息包括服务器上引发的异常的详细信息。
                config.EnableDetailedErrors = true;
#endif
                //如果客户端在此时间间隔内未收到消息（包括保持活动状态），则服务器会将客户端视为已断开连接。默认超时为30秒。
                //由于实现方式的原因，客户端实际标记为断开连接可能需要更长的时间。建议值为 KeepAliveInterval 值的两倍。
                if (Options.SignalR.ClientTimeoutInterval > 0)
                {
                    config.ClientTimeoutInterval = TimeSpan.FromSeconds(Options.SignalR.ClientTimeoutInterval);
                }
                //如果客户端在此时间间隔内未发送初始握手消息，连接将关闭。 这是一种高级设置，只应在握手超时错误由于严重网络延迟而发生时进行修改。默认超时为15秒。
                //有关握手过程的详细信息，请参阅SignalR 集线器协议规范。
                if (Options.SignalR.HandshakeTimeout > 0)
                {
                    config.HandshakeTimeout = TimeSpan.FromSeconds(Options.SignalR.HandshakeTimeout);
                }
                //如果服务器未在此时间间隔内发送消息，则会自动发送 ping 消息，使连接保持打开状态。默认间隔为15秒。
                //更改 KeepAliveInterval时，请更改客户端上的 ServerTimeout/serverTimeoutInMilliseconds 设置。
                //建议的 ServerTimeout/serverTimeoutInMilliseconds 值为 KeepAliveInterval 值的双精度值。
                if (Options.SignalR.KeepAliveInterval > 0)
                {
                    config.KeepAliveInterval = TimeSpan.FromSeconds(Options.SignalR.KeepAliveInterval);
                }
                //设置单个传入集线器消息的最大消息大小。默认值为32KB。
                if (Options.SignalR.MaximumReceiveMessageSize > 0)
                {
                    config.MaximumReceiveMessageSize = Options.SignalR.MaximumReceiveMessageSize;
                }
                //设置客户端上载流的最大缓冲区大小。默认大小为10。
                if (Options.SignalR.StreamBufferCapacity > 0)
                {
                    config.StreamBufferCapacity = Options.SignalR.StreamBufferCapacity;
                }

                ////设置受支持的集线器协议名称的集合。
                ////无需设置，使SignalR默认支持如下三种底层传输技术，默认回落机制, SignalR有能力去协商支持的传输类型。
                ////Web Socket, Server Sent Events 和 Long Polling。
                ////Web Socket是最好的最有效的传输方式, 如果浏览器或Web服务器不支持它的话, 就会降级使用SSE, 实在不行就用Long Polling。
                //config.SupportedProtocols = new[]
                //{
                //    "WebSocket",
                //    "ServerSentEvents",
                //    "LongPolling",
                //};

            };
        }

        /// <summary>
        /// 重写以获取SignalR服务器创建委托
        /// </summary>
        /// <param name="services">依赖注入服务容器</param>
        /// <returns></returns>
        protected virtual Action<ISignalRServerBuilder> GetSignalRServerBuildAction(IServiceCollection services)
        {
            MicroCloudOptions MicroCloud = services.GetMicroCloudOptions();
            return builder => builder.AddNewtonsoftJsonProtocol(options =>
            {
                if (MicroCloud.Mvc?.IsLowercaseJsonProperty == true)
                {
                    //修改属性名称的序列化方式，首字母小写，即驼峰样式
                    options.PayloadSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                }
                else
                {
                    //解决命名不一致问题 
                    options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
                }

                //循环引用处理
                if (MicroCloud.Mvc?.ReferenceLoopHandling.IsMissing() == false)
                {
                    var referenceLoopHandling = MicroCloud.Mvc?.ReferenceLoopHandling.ToLower();
                    switch (referenceLoopHandling)
                    {
                        case "error":
                            options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Error;
                            break;
                        case "ignore":
                            options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                            break;
                        case "serialize":
                            options.PayloadSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Serialize;
                            break;
                    }
                }

                //Null值处理
                if (MicroCloud.Mvc?.NullValueHandling.IsMissing() == false)
                {
                    var nullValueHandling = MicroCloud.Mvc?.NullValueHandling.ToLower();
                    switch (nullValueHandling)
                    {
                        case "include":
                            options.PayloadSerializerSettings.NullValueHandling = NullValueHandling.Include;
                            break;
                        case "ignore":
                            options.PayloadSerializerSettings.NullValueHandling = NullValueHandling.Ignore;
                            break;
                    }
                }

                // 日期时间格式化处理 - 方式1
                //options.SerializerSettings.Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy/MM/dd HH:mm:ss" });
                // 日期时间格式化处理 - 方式2
                if (MicroCloud.Mvc?.DateFormatHandling.IsMissing() == false)
                {
                    var dateFormatHandling = MicroCloud.Mvc?.DateFormatHandling.ToLower();
                    switch (dateFormatHandling)
                    {
                        case "iso":
                            options.PayloadSerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
                            break;
                        case "microsoft":
                            options.PayloadSerializerSettings.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
                            break;
                    }
                }
                // 日期时间格式化字符串处理
                if (MicroCloud.Mvc?.DateFormatString.IsMissing() == false)
                {
                    var dateFormatString = MicroCloud.Mvc?.DateFormatString;
                    options.PayloadSerializerSettings.DateFormatString = dateFormatString;
                }
                // 日期时间时区处理
                if (MicroCloud.Mvc?.DateTimeZoneHandling.IsMissing() == false)
                {
                    var dateTimeZoneHandling = MicroCloud.Mvc?.DateTimeZoneHandling.ToLower();
                    switch (dateTimeZoneHandling)
                    {
                        case "local":
                            options.PayloadSerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
                            break;
                        case "utc":
                            options.PayloadSerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
                            break;
                        case "unspecified":
                            options.PayloadSerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Unspecified;
                            break;
                        case "roundtripkind":
                            options.PayloadSerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
                            break;
                    }

                }
                // 日期时间解析处理
                if (MicroCloud.Mvc?.DateParseHandling.IsMissing() == false)
                {
                    var dateParseHandling = MicroCloud.Mvc?.DateParseHandling.ToLower();
                    switch (dateParseHandling)
                    {
                        case "none":
                            options.PayloadSerializerSettings.DateParseHandling = DateParseHandling.None;
                            break;
                        case "datetime":
                            options.PayloadSerializerSettings.DateParseHandling = DateParseHandling.DateTime;
                            break;
                        case "datetimeoffset":
                            options.PayloadSerializerSettings.DateParseHandling = DateParseHandling.DateTimeOffset;
                            break;
                    }

                }

            });
        }

        /// <summary>
        /// 应用模块服务
        /// </summary>
        /// <param name="app">应用程序</param>
        public override void UsePack(WebApplication app)
        {
            //IServiceProvider provider = app.ApplicationServices;
            ////添加WebSocket支持，SignalR优先使用WebSocket传输
            //app.UseWebSockets();

            IsEnabled = true;
        }

        #endregion
    }
}
